#!/usr/bin/perl
use strict;
use warnings;

$|++;

# program name: expand-deny-messages

# DOCUMENTATION IS AT THE BOTTOM OF THIS FILE; PLEASE READ

use lib $ENV{GL_LIBDIR};
use Gitolite::Rc;
use Gitolite::Common;

my %attempted_access = (
    # see triggers.html
    'ACCESS_1' => {
        'R' => 'Repo read',
        'W' => 'Repo write',
    },
    'ACCESS_2' => {
        'W' => "Fast forward push",
        '+' => "Rewind push branch or overwrite tag",
        'C' => "Create ref",
        'D' => "Delete ref",
    }
);

# env var to disable is set?
exit 0 if $ENV{GL_OPTION_EDM_DISABLE};

# argument 1
my $a12 = shift;    # ACCESS_1 or ACCESS_2
exit 0 if $a12 !~ /^ACCESS_[12]$/;    # shouldn't happen; error in rc file?

# the rest of the arguments
my ( $repo, $user, $aa, $ref, $msg, $oldsha, $newsha ) = @ARGV;

# we're only interested in deny messages
exit 0 if $msg !~ /DENIED/;

print STDERR "\nFATAL -- ACCESS DENIED\n";

_info( "Repo",      $repo );
_info( "User",      $user );
_info( "Stage",     ( $a12 eq 'ACCESS_1' ? "Before git was called" : "From git's update hook" ) );
_info( "Ref",       _ref($ref) ) if $a12 eq 'ACCESS_2';
_info( "Operation", _op( $a12, $aa, $oldsha, $newsha ) );

if ( $ref =~ m((^VREF/[^/]+)) ) {
    my $vref      = $1;
    if ($ref =~ s(^VREF/NAME/)()) {
        print STDERR "You're apparently not allowed to push '$ref'";
    } else {
        my $vref_text = slurp( _which( $vref, 'x' ) );
        my $etag      = '(?:help|explain|explanation)';
        $vref_text =~ m(^\s*# $etag.start\n(.*)^\s*# $etag.end\n)sm
          and print STDERR "Explanation for $vref:\n$1";
  }
}

print STDERR "\n";
print STDERR "$ENV{GL_OPTION_EDM_EXTRA_INFO}\n\n" if $ENV{GL_OPTION_EDM_EXTRA_INFO};

# ------------------------------------------------------------------------

sub _ref {
    my $r = shift;
    return "VREF '$r'"   if $r =~ s(^VREF/)();
    return "Branch '$r'" if $r =~ s(^refs/heads/)();
    return "Tag '$r'"    if $r =~ s(^refs/tags/)();
    return "Non-standard ref '$r'";
}

sub _info {
    printf STDERR "%-14s  %-60s\n", @_;
}

sub _op {
    my ( $a12, $aa, $oldsha, $newsha ) = @_;

    # first remove the M part and save the text for later addition if needed
    my $merge = ( $aa =~ s/M// ? " with merge commit" : "" );

    # next, the attempted access is modified to reflect the actual operation being
    # attempted.  NOTE: this no longer necessarily reflects what the gitolite log
    # file stores; it's more granular and truly distinguishes a branch create from
    # an ff push, etc.  Could help when user typos a branch name I suppose
    $aa = 'C' if $oldsha and $oldsha eq '0' x 40;
    $aa = 'D' if $newsha and $newsha eq '0' x 40;

    # then we map it, add merge text if any
    my $op = $attempted_access{$a12}{$aa} || "Unknown operation '$aa'";
    $op .= $merge;

    return $op;
}

__END__

ENABLING THE FEATURE
--------------------

To enable this feature, uncomment the line in the rc file if your gitolite was
installed recently enough.  Otherwise you will need to add these lines to the
end of your rc file, just before the "%RC" block ends:

    ACCESS_1 => [
        'expand-deny-messages',
    ],

    ACCESS_2 => [
        'expand-deny-messages',
    ],

Please don't miss the trailing commas!

DISABLING IT FOR SPECIFIC REPOS
-------------------------------

Once it is enabled at the rc file level, if you wish to disable it for
specific repositories just add a line like this to those repos:

        option ENV.EDM_DISABLE = 1

Or you can also disable it for all repos, then enable it for some:

    repo @all
        option ENV.EDM_DISABLE = 1

    # ... then later ...

    repo foo bar @baz
        option ENV.EDM_DISABLE = 0

(options.html[1] and pages linked from it will explain how that works).

[1]: http://gitolite.com/gitolite/options.html

SUPPLYING EXTRA INFORMATION
---------------------------

You can also supply some extra information to be printed, by adding a line
like this to each repository in the gitolite.conf file:

        option ENV.EDM_EXTRA_INFO = "please contact alice@example.com"

You could of course add it under a "repo @all" section if you like.

SUPPLYING EXTRA INFORMATION FOR VREFs
-------------------------------------

If you have VREFs that do funky things and you want to **lecture** your users
when they screw up, add something like the following to your VREF code.

    # help start

    Some help text.

    Some more help text.  This can be
    multi-line.

    (etc etc etc)

    # help end

Then everything between the "# help start" line and the "# help end" line will
get printed if a users falls afoul of this VREF.  If any of the lines shown
are not valid syntax for your language, figure out some way to put the whole
thing in a comment block.  Here a C example:

    /*
    # help start
    line 1
    line 2
    ...
    last line
    # help end
    */

Even if your language does not support multi-line comments like C does, there
may be other ways to specify those lines.  Here's an example in shell:

    cat << EOF > /dev/null
    # help start
    line 1
    line 2
    ...
    last line
    # help end
    EOF
